/*
 * Copyright Konstantin Triger <kostat@gmail.com> 
 * 
 * This file is part of Jaque - JAva QUEry library <http://code.google.com/p/jaque/>.
 * 
 * Jaque is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jaque is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jaque;

import static jaque.expressions.Expression.*;
import java.lang.reflect.Method;
import jaque.expressions.*;
import jaque.functions.Function;

public final class DynamicOperation {
	private DynamicOperation() {
	}

	// operation
	private static final Method _all;
	private static final Method _any;
	private static final Method _average1;
	private static final Method _average2;

	private static final Method _count1;
	private static final Method _count2;
	private static final Method _contains1;
	private static final Method _contains2;
	private static final Method _elementAt1;
	private static final Method _elementAt2;
	private static final Method _first1;
	private static final Method _first2;
	private static final Method _firstOrNull1;
	private static final Method _firstOrNull2;
	private static final Method _isEmpty;
	private static final Method _last1;
	private static final Method _last2;
	private static final Method _lastOrNull1;
	private static final Method _lastOrNull2;
	private static final Method _longCount1;
	private static final Method _longCount2;
	private static final Method _max1;
	private static final Method _max2;
	private static final Method _max3;
	private static final Method _min1;
	private static final Method _min2;
	private static final Method _min3;
	private static final Method _single1;
	private static final Method _single2;
	private static final Method _singleOrNull1;
	private static final Method _singleOrNull2;
	private static final Method _sum1;
	private static final Method _sum2;

	static {
		try {
			_all = Operation.class.getDeclaredMethod("all", new Class<?>[] {
					Function.class, Iterable.class });
			_any = Operation.class.getDeclaredMethod("any", new Class<?>[] {
					Function.class, Iterable.class });
			_average1 = Operation.class.getDeclaredMethod("average",
					new Class<?>[] { Iterable.class });
			_average2 = Operation.class.getDeclaredMethod("average",
					new Class<?>[] { Function.class, Iterable.class });
			_count1 = Operation.class.getDeclaredMethod("count",
					new Class<?>[] { Iterable.class });
			_count2 = Operation.class.getDeclaredMethod("count",
					new Class<?>[] { Function.class, Iterable.class });
			_contains1 = Operation.class.getDeclaredMethod("contains",
					new Class<?>[] { Comparable.class, Iterable.class });
			_contains2 = Operation.class.getDeclaredMethod("contains",
					new Class<?>[] { Object.class, java.util.Comparator.class,
							Iterable.class });
			_elementAt1 = Operation.class.getDeclaredMethod("elementAt",
					new Class<?>[] { Integer.TYPE, Iterable.class });
			_elementAt2 = Operation.class.getDeclaredMethod("elementAt",
					new Class<?>[] { Integer.TYPE, Function.class,
							Iterable.class });
			_first1 = Operation.class.getDeclaredMethod("first",
					new Class<?>[] { Iterable.class });
			_first2 = Operation.class.getDeclaredMethod("first",
					new Class<?>[] { Function.class, Iterable.class });
			_firstOrNull1 = Operation.class.getDeclaredMethod("firstOrNull",
					new Class<?>[] { Iterable.class });
			_firstOrNull2 = Operation.class.getDeclaredMethod("firstOrNull",
					new Class<?>[] { Function.class, Iterable.class });
			_isEmpty = Operation.class.getDeclaredMethod("isEmpty",
					new Class<?>[] { Iterable.class });
			_last1 = Operation.class.getDeclaredMethod("last",
					new Class<?>[] { Iterable.class });
			_last2 = Operation.class.getDeclaredMethod("last", new Class<?>[] {
					Function.class, Iterable.class });
			_lastOrNull1 = Operation.class.getDeclaredMethod("lastOrNull",
					new Class<?>[] { Iterable.class });
			_lastOrNull2 = Operation.class.getDeclaredMethod("lastOrNull",
					new Class<?>[] { Function.class, Iterable.class });
			_longCount1 = Operation.class.getDeclaredMethod("longCount",
					new Class<?>[] { Iterable.class });
			_longCount2 = Operation.class.getDeclaredMethod("longCount",
					new Class<?>[] { Function.class, Iterable.class });
			_max1 = Operation.class.getDeclaredMethod("max",
					new Class<?>[] { Iterable.class });
			_max2 = Operation.class.getDeclaredMethod("max", new Class<?>[] {
					Function.class, Iterable.class });
			_max3 = Operation.class.getDeclaredMethod("max",
					new Class<?>[] { Function.class,
							java.util.Comparator.class, Iterable.class });
			_min1 = Operation.class.getDeclaredMethod("min",
					new Class<?>[] { Iterable.class });
			_min2 = Operation.class.getDeclaredMethod("min", new Class<?>[] {
					Function.class, Iterable.class });
			_min3 = Operation.class.getDeclaredMethod("min",
					new Class<?>[] { Function.class,
							java.util.Comparator.class, Iterable.class });
			_single1 = Operation.class.getDeclaredMethod("single",
					new Class<?>[] { Iterable.class });
			_single2 = Operation.class.getDeclaredMethod("single",
					new Class<?>[] { Function.class, Iterable.class });
			_singleOrNull1 = Operation.class.getDeclaredMethod("singleOrNull",
					new Class<?>[] { Iterable.class });
			_singleOrNull2 = Operation.class.getDeclaredMethod("singleOrNull",
					new Class<?>[] { Function.class, Iterable.class });
			_sum1 = Operation.class.getDeclaredMethod("sum",
					new Class<?>[] { Iterable.class });
			_sum2 = Operation.class.getDeclaredMethod("sum", new Class<?>[] {
					Function.class, Iterable.class });
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

	public static <E> boolean all(final Function<Boolean, ? super E> predicate,
			final Queryable<E> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);
		// Class<E> eType = source.getElementType();
		// if (eType == null)
		// eType = (Class<E>) lambda.getParameters().get(0).getResultType();
		Expression e = invoke(null, _all, quote(lambda), source.getExpression());
		return source.getFactory().createQueryable(Boolean.TYPE, e).single();
	}

	public static <E> boolean any(final Function<Boolean, ? super E> predicate,
			final Queryable<E> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _any, quote(lambda), source.getExpression());
		return source.getFactory().createQueryable(Boolean.TYPE, e).single();
	}

	public static <T extends Number> Number average(final Queryable<T> source) {
		Expression e = invoke(null, _average1, source.getExpression());
		return (Number) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	public static <T, E extends Number> Number average(
			final Function<E, ? super T> selector, final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(selector);
		Expression e = invoke(null, _average2, quote(lambda), source
				.getExpression());
		return (Number) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	public static <T extends Comparable<? super T>> boolean contains(
			final T value, final Queryable<T> source) {

		Expression e = invoke(null, _contains1, constant(value), source
				.getExpression());
		return source.getFactory().createQueryable(Boolean.TYPE, e).single();
	}

	public static <T> boolean contains(final T value,
			final java.util.Comparator<? super T> comparator,
			final Queryable<T> source) {

		Expression e = invoke(null, _contains2, constant(value),
				constant(comparator), source.getExpression());
		return source.getFactory().createQueryable(Boolean.TYPE, e).single();
	}

	public static <E> int count(final Function<Boolean, ? super E> predicate,
			final Queryable<E> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _count2, quote(lambda), source
				.getExpression());
		return ((Number) source.getFactory().createQueryable(Integer.TYPE, e)
				.single()).intValue();
	}

	public static <E> int count(final Queryable<E> source) {

		Expression e = invoke(null, _count1, source.getExpression());
		return ((Number) source.getFactory().createQueryable(Integer.TYPE, e)
				.single()).intValue();
	}

	@SuppressWarnings("unchecked")
	public static <T> T elementAt(int position, final Queryable<T> source) {
		Expression e = invoke(null, _elementAt1, constant(position,
				Integer.TYPE), source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T elementAt(int position,
			final Function<Boolean, ? super T> predicate,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);
		Expression e = invoke(null, _elementAt2, constant(position,
				Integer.TYPE), quote(lambda), source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T first(final Queryable<T> source) {

		Expression e = invoke(null, _first1, source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T first(final Function<Boolean, ? super T> predicate,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _first2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T firstOrNull(final Queryable<T> source) {

		Expression e = invoke(null, _firstOrNull1, source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T firstOrNull(
			final Function<Boolean, ? super T> predicate,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _firstOrNull2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	public static <T> boolean isEmpty(final Queryable<T> source) {
		Expression e = invoke(null, _isEmpty, source.getExpression());
		return source.getFactory().createQueryable(Boolean.TYPE, e).single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T last(final Queryable<T> source) {

		Expression e = invoke(null, _last1, source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T last(final Function<Boolean, ? super T> predicate,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _last2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T lastOrNull(final Queryable<T> source) {

		Expression e = invoke(null, _lastOrNull1, source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T lastOrNull(
			final Function<Boolean, ? super T> predicate,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _lastOrNull2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	public static <E> long longCount(
			final Function<Boolean, ? super E> predicate,
			final Queryable<E> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _longCount2, quote(lambda), source
				.getExpression());
		return ((Number) source.getFactory().createQueryable(Long.TYPE, e)
				.single()).longValue();
	}

	public static <E> long longCount(final Queryable<E> source) {

		Expression e = invoke(null, _longCount1, source.getExpression());
		return ((Number) source.getFactory().createQueryable(Long.TYPE, e)
				.single()).longValue();
	}

	@SuppressWarnings("unchecked")
	public static <T extends Comparable<? super T>> T max(
			final Queryable<T> source) {
		Expression e = invoke(null, _max1, source.getExpression());
		return (T) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T, Key extends Comparable<? super Key>> T max(
			final Function<Key, ? super T> selector, final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(selector);
		Expression e = invoke(null, _max2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T, Key> T max(final Function<Key, ? super T> selector,
			final java.util.Comparator<? super Key> comparator,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(selector);
		Expression e = invoke(null, _max3, quote(lambda), constant(comparator),
				source.getExpression());
		return (T) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T extends Comparable<? super T>> T min(
			final Queryable<T> source) {
		Expression e = invoke(null, _min1, source.getExpression());
		return (T) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T, Key extends Comparable<? super Key>> T min(
			final Function<Key, ? super T> selector, final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(selector);
		Expression e = invoke(null, _min2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T, Key> T min(final Function<Key, ? super T> selector,
			final java.util.Comparator<? super Key> comparator,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(selector);
		Expression e = invoke(null, _min3, quote(lambda), constant(comparator),
				source.getExpression());
		return (T) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T single(final Queryable<T> source) {

		Expression e = invoke(null, _single1, source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T single(final Function<Boolean, ? super T> predicate,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _single2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T singleOrNull(final Queryable<T> source) {

		Expression e = invoke(null, _singleOrNull1, source.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T> T singleOrNull(
			final Function<Boolean, ? super T> predicate,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(predicate);

		Expression e = invoke(null, _singleOrNull2, quote(lambda), source
				.getExpression());
		return (T) source.getFactory().createQueryable(Object.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T, Result extends Number> Result sum(
			final Queryable<T> source) {

		Expression e = invoke(null, _sum1, source.getExpression());
		return (Result) source.getFactory().createQueryable(Number.class, e)
				.single();
	}

	@SuppressWarnings("unchecked")
	public static <T, Result extends Number> Result sum(
			final Function<Result, ? super T> selector,
			final Queryable<T> source) {
		LambdaExpression<?> lambda = LambdaExpression.parse(selector);

		Expression e = invoke(null, _sum2, quote(lambda), source
				.getExpression());
		return (Result) source.getFactory().createQueryable(Number.class, e)
				.single();
	}
}
